package de.tu_darmstadt.informatik.tk.lernsystem.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.htmlparser.Node;
import org.htmlparser.Tag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.htmlparser.visitors.NodeVisitor;

/**
 * This class takes a list of DOM Nodes and an XPath-String,
 * parses the DOM tree and allows access to all the
 * nodes affected by the XPath string.
 */
public class XpathFilter {
	private final NodeList nodeList;
	private final List<XpathNode> xpathNodes = new ArrayList<XpathFilter.XpathNode>();
	private final List<Tag> result = new ArrayList<Tag>();
	
	/**
	 * @param nodeList DOM tree
	 * @param xpath Xpath
	 */
	public XpathFilter(NodeList nodeList, String xpath) {
		this.nodeList = nodeList;
		if (xpath.startsWith("//")) {
			xpath = xpath.substring(2);
		}
		String[] tokens = xpath.split("/");
		for (int i=0; i<tokens.length; i++) {
			xpathNodes.add(createNodeFromString(tokens[i]));
		}
	}
	
	/**
	 * @return All DOM nodes matched by the XPath
	 */
	public List<Tag> getResult() {
		return result;
	}
	
	private boolean checkMatch(Tag tag, int pos) {
		XpathNode xpathNode = xpathNodes.get(pos);
		if (xpathNode.getTag().isEmpty()) {
			return true;
		}
		if (!xpathNode.getTag().toUpperCase().equals(tag.getTagName().toUpperCase())) {
			return false;
		}
		if (xpathNode.getElementNum() > 0) {
			int elNum = 1;
			Node prevNode = tag.getPreviousSibling();
			while (prevNode != null) {
				if (prevNode instanceof Tag) {
					Tag prev = (Tag)prevNode;
					if (tag.getTagName().equals(prev.getTagName())) {
						elNum++;
					}
				}
				prevNode = prevNode.getPreviousSibling();
			}
			if (elNum != xpathNode.getElementNum()) {
				return false;
			}
		}
		for (String key : xpathNode.getAttributes().keySet()) {
			String value = xpathNode.getAttributes().get(key);
			if (!value.equals(tag.getAttribute(key))) {
				return false;
			}
		}
		if (pos > 0) {
			return checkMatch((Tag)tag.getParent(), pos-1);
		}
		return true;
	}
	
	/**
	 * Parses the DOM tree
	 * @throws ParserException
	 */
	public void parse() throws ParserException {
		nodeList.visitAllNodesWith(new NodeVisitor() {
			@Override
	        public void visitTag(Tag tag) {
				if (checkMatch(tag, xpathNodes.size()-1)) {
					result.add(tag);
				}
			}
		});
	}
	
	private XpathNode createNodeFromString(String s) {
		if (s.isEmpty()) {
			return new XpathNode("");
		}
		String[] tokens = s.split("\\[");
		XpathNode node = new XpathNode(tokens[0]);
		if (tokens.length > 1) {
			tokens[1] = tokens[1].substring(0, tokens[1].length()-1);
			if (tokens[1].startsWith("@")) {
				tokens[1] = tokens[1].substring(1);
				String[] keyVal = tokens[1].split("=");
				if (keyVal[1].startsWith("\"") && keyVal[1].endsWith("\"")) {
					keyVal[1] = keyVal[1].substring(1, keyVal[1].length()-1);
				}
				node.addAttribute(keyVal[0], keyVal[1]);
			} else {
				node.setElementNum(Integer.valueOf(tokens[1]));
			}
		}
		return node;
	}
	
	/**
	 * Helper class
	 */
	private class XpathNode {
		private final String tag;
		private int elementNum = 0;
		private Map<String, String> attributes = new HashMap<String, String>();
		
		public XpathNode(String tag) {
			this.tag = tag;
		}
		
		public void setElementNum(int num) {
			this.elementNum = num;
		}
		
		public void addAttribute(String key, String value) {
			attributes.put(key, value);
		}

		public String getTag() {
			return tag;
		}
		
		public int getElementNum() {
			return elementNum;
		}
		
		public Map<String, String> getAttributes() {
			return attributes;
		}
	}
}
